#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
 Pyjon parsed tree into JooScript syntax converter.

 It actually converts special calls into JooScript syntax, "var" into "let",
 and replacing namespaces.

 Author: Evgen Burzak <buzzilo@gmail.com>
"""

import sys, re
from pyjon import node2js, interpr, jsparser
import joo_node2js, defs, jooparser

NodeToJS = node2js.NodeToJS

# constants from joo-base.js
type_ = dict(
    NONE = 0,
    OBJECT = 0x1,
    NUMBER = 0x2 ,
    INFINITY = 0x2 | 0x80 ,
    NAN = 0x2 | 0x100,
    INT = 0x2 | 0x4,
    UINT = 0x2 | 0x8,
    FLOAT = 0x2 | 0x10,
    BOOLEAN = 0x2 | 0x20,
    STRING = 0x40,
    FUNCTION = 0x1 | 0x200,
    CLASS = 0x1 | 0x1 | 0x200 | 0x400,
    XML = 0x1 | 0x800,
)

canonical_unit_obj = joo_node2js.unit_obj
canonical_root_obj = joo_node2js.root_obj
canonical_glob_obj = joo_node2js.glob_obj
canonical_group_obj = joo_node2js.group_obj
canonical_class_obj = joo_node2js.class_obj

unit_scope = {
    "Role" : {},
    "Type" : {},
    "Class" : {},
    "XML" : {},
}

global_scope = {
    "$Event" : {},
    "$Package" : {},
    "$Import" : {}
}

global_scope.update(unit_scope)

varName_ = "@?[a-zA-Z\x7f-\xff0-9_\$]+"
varNameRegex = re.compile('^'+varName_+'$')

def type_to_class(x):
    if x == 0: return "none"
    for k, v in type_.iteritems():
        if v == x:
            return k.title()
    return x

# TODO collapse parens where possible while translating
class JSNodeToJoo(NodeToJS):
    """
    JSNodeToJoo takes JS node and converts it into Joo syntax
    """
    def __init__(self, parent): 
        NodeToJS.__init__(self, parent)
        self.paren_free_mode = True
        if parent: 
            self.scope = dict(**parent.scope)
        else:
            self.scope = {}
        self.unit_obj = getattr(parent, "unit_obj", None)
        self.root_obj = getattr(parent, "root_obj", None)
        self.glob_obj = getattr(parent, "glob_obj", None)
        self.group = None
        self.group_obj = getattr(parent, "group_obj", None)
        self.class_obj = getattr(parent, "class_obj", None)
        self.virtualNamespaces = []

    def convert_unit(self, n, i, c):
        self.check(n,subnodes=2)
        args = n[1]
        assert len(args) == 3, "unexpected length of unit arguments"
        assert args[2].type == "FUNCTION", "expected function"
        path_ = self.o(args[0], i, c)
        file_ = self.o(args[1], i, c)
        unit = args[2]
        body = unit.body
        unit_obj = root_obj = glob_obj = None

        if len(unit.params) >= 1:
            unit_obj = unit.params[0]
            """
            if unit_obj != joo_node2js.JooNodeToJS.unit_obj:
                defs.err("WARNING: unit object name is differ from " + \
                            joo_node2js.JooNodeToJS.unit_obj)
            """

        if len(unit.params) >= 2:
            root_obj = unit.params[1]
            """
            if root_obj != joo_node2js.JooNodeToJS.root_obj:
                defs.err("WARNING: root object name is differ from " + \
                            joo_node2js.JooNodeToJS.root_obj)
            """

        if len(unit.params) == 3:
            glob_obj = unit.params[2]
            """
            if glob_obj != joo_node2js.JooNodeToJS.glob_obj:
                defs.err("WARNING: glob object name is differ from " + \
                            joo_node2js.JooNodeToJS.glob_obj)
            """

        ctx = UnitContext(self, path_, file_, unit_obj, root_obj, glob_obj, body)
        return ctx.convert_unit(i,c)

    def convert_package(self, n, i, c):
        self.check(n,subnodes=2)
        context = n[0]
        # Is unit.Package(...) or $Package(...)?
        method_name = self.context_var(context)
        if method_name and method_name.value in ("Package", "$Package"):
            ctx = PackageContext(self, n[1])
            return ctx.convert_package(i,c)
        
        else:
            return self.convert_function_call(n,i,c)

    def convert_class(self, n, i, c):
        context = n[0]
        # Is unit.buildClass(...) or $buildClass?
        if (len(context) == 2 and context[0].value == self.unit_obj) or \
                context.type == "IDENTIFIER":
            self.check(n,subnodes=2)
            ctx = ClassContext(self, n[1])
            return ctx.convert_class(i, c)

        else:
            return self.convert_function_call(n,i,c)

    def convert_role(self, n, i, c):
        context = n[0]
        class_ = self.context_var(context)
        if class_ and class_.value == "Role":
            self.check(n, optattrs=["destructurings"], subnodes=2)
            ctx = RoleContext(self, len(n[1]) and n[1][0] or None)
            return ctx.convert_role(i, c)
            #return "role %s" % self.o(n[1][0], i, c)

        else:
            return self.convert_function_call(n,i,c)

    def convert_function(self, n, i, c):
        ctx = FunctionContext(self, n)
        return ctx.convert_function(i,c)

    def convert_import(self, n, i, c):
        context = n[0]
        args = n[1]
        if isinstance(self, UnitContext) and len(context) == 2 and \
                context[0].value in (self.unit_obj, "this"):
            self.check(n,subnodes=2)
            return "import %s" % ", ".join([x.value for x in args])

        else:
            return self.convert_function_call(n,i,c)

    def convert_event(self, n, i, c, method):
        self.check(n,subnodes=2)
        if method in ("when", "once"):
            args = n[1]
            if len(args) == 2:
                evtype = args[0]
                if args[1].type == "FUNCTION":
                    fun = args[1]
                    ctx = FunctionContext(self, fun)
                    if len(fun.params) > 0:
                        evobj = fun.params[0]
                        if evobj not in ctx.scope:
                            ctx.scope[evobj] = {"type":None}
                        ctx.scope[evobj]["alias"] = "event"
                        #ctx.varSubs[evobj] = "event"
                    handler = ctx.o(fun.body,i,c)
                else:
                    handler = self.o(args[1],i,c)

                return "%s %s do %s" % (method, self.o(evtype, i, c), handler)

            elif len(args) == 3:
                raise node2js.OtherError, "Not implemented"

        elif method in ("dispatch", "dismiss"):
            args = n[1]
            thread = None
            if len(args) > 1:
                thread = self.o(args[1], i, c)
            evtype = self.o(args[0], i, c)
            if thread:
                return "%s %s %s %s" % (method, evtype, method == "dispatch" and "to" or "from", thread)
            else:
                return "%s %s" % (method, evtype)

        elif method == "addEventListener": 
            args = n[1]
            #if args[0].type == "STRING" and args[1].type == "FUNCTION" and \
                    #args[2].type in ("TRUE", "FALSE"):
            obj = self.o(n[0][0],i,c)
            use_capture = args[2].type == "TRUE" and "use capture, " or ""
            evtype = self.o(args[0],i,c)
            if re.search(r'mouse', evtype): on = "over" 
            elif re.search(r'message|error', evtype): on = "from"
            else: on = "on"
            if args[1].type == "FUNCTION":
                fun = args[1]
                ctx = FunctionContext(self, fun)
                if len(fun.params) > 0:
                    evobj = fun.params[0]
                    if evobj not in ctx.scope:
                        ctx.scope[evobj] = {"type":None}
                    ctx.scope[evobj]["alias"] = "event"
                    #ctx.varSubs[evobj] = "event"
                handler = ctx.o(fun.body,i,c)
            else:
                handler = self.o(args[1],i,c)

            return "%swhen %s %s %s do %s" % (use_capture, evtype, on,
                    obj, handler)

        return self.convert_function_call(n,i,c)

    def convert_hashtable(self, n, i, c, obj, var):
        self.check(n,subnodes=2)
        args = n[1]
        assert args.type == "LIST", "unexpected " + args.type 
        if obj in (self.unit_obj, canonical_unit_obj, "this"):
            assert args[0].type == "ARRAY_INIT", "unexpected " + args[0].type 
            table = []
            i = 0
            for entry in args[0]:
                #assert entry.type == "ARRAY_INIT", "unexpected " + entry.type 
                if entry.type == "ARRAY_INIT" and len(entry) == 2:
                    if entry[0].type == "NUMBER" and entry[0].value == i:
                        table.append(self.o(entry[1],i,c))
                    else:
                        table.append(" => ".join([self.o(entry[0],i,c), self.o(entry[1],i,c)]))
                else:
                    assert False, "unexpected " + entry
                #else:
                    #table.append(self.o(entry,i,c))
                i += 1
            return "(%s%s)" % (", ".join(table), i==1 and "," or "")

        else:
            if var.type == "STRING": var = str(var.value)
            elif var.type == "IDENTIFIER": var = self.convert_context_var(var.value)
            else: var = self.o(var,i,c)
            # query hash
            if len(args) == 1:
                return "%s[%s]" % (var, self.o(args,i,c))
            # assign hash
            elif len(args) == 2:
                return "%s[%s]=%s" % (var, self.o(args[0],i,c), self.o(args[1],i,c))

    def convert_and(self, n, i, c):
        self.check(n,subnodes=2)
        return "%s and %s" % (self.o(n[0],i,c), self.o(n[1],i,c))

    def is_function_node(self, n):
        return n and n.type == "FUNCTION"

    def is_class_node(self, n):
        # fixme $buildClass(...)
        if n and \
               n.type == "CALL" and \
               n[0].type == "DOT" and \
               n[0][0].value == self.unit_obj and \
               n[0][1].value == "buildClass":
            return True
        return False

    def is_role_node(self, n):
        var = n and n.type == "CALL" and self.context_var(n[0])
        if var and var.value == "Role": return True
        return False

    def convert_var(self, n, i, c):
        initializer = len(n) == 1 and getattr(n[0], "initializer", None)
        var_name = n[0].name
        if self.is_class_node(initializer):
            #self.scope[var_name] = {}
            ctx = ClassContext(self, initializer[1])
            # check for var name is equal to class name or just silently skip it
            if ctx.className == var_name:
                self.check(n, attrs=["destructurings"], subnodes=1)
                return ctx.convert_class(i, c)

        elif self.is_role_node(initializer):
            self.check(n, attrs=["destructurings"], subnodes=1)
            #self.scope[var_name] = {}
            ctx = RoleContext(self, len(initializer[1]) and initializer[1][0] or None)
            return ctx.convert_role(i, c)

        #for x in n:
            #self.scope[x.name] = {}

        # var is deprecated in Joo
        n.value = "let"

        return NodeToJS.convert_var(self, n, i, c)

    def convert_or(self, n, i, c):
        self.check(n,subnodes=2)
        return "%s or %s" % (self.o(n[0],i,c), self.o(n[1],i,c))

    def convert_not(self, n, i, c):
        self.check(n,subnodes=1)
        return "not %s" % (self.o(n[0],i,c))

    def convert_strict_eq(self, n, i, c):
        self.check(n,subnodes=2)
        return "%s is %s" % (self.o(n[0],i,c), self.o(n[1],i,c))

    def convert_call(self, n, i, c):
        obj = None
        var = None
        if n[0].type == "IDENTIFIER":
            method = n[0].value[0] == "$" and n[0].value[1:] or n[0].value
        elif n[0].type in ("DOT", "INDEX"):
            #print "!", n[0]
            if n[0][0].type == "IDENTIFIER":
                obj = n[0][0].value
                var = n[0][0]
            elif n[0][0].type == "THIS":
                obj = "this"
            elif n[0][0].type in ("DOT", "INDEX"):
                var = self.context_var(n[0][0])
                if var: obj = var.value
            method = n[0][-1].value
        else:
            method = None

        #print obj, method

        if method == "Unit":
            return self.convert_unit(n,i,c)

        elif method == "Package":
            return self.convert_package(n,i,c)

        elif method == "buildClass":
            return self.convert_class(n,i,c)

        elif method == "Import":
            return self.convert_import(n,i,c)

        elif method == "Role":
            return self.convert_role(n,i,c)

        elif (method in ("when", "once", "dispatch", "dismiss") and \
                obj in ("$Event", "Event")) or \
                method == "addEventListener":
            return self.convert_event(n,i,c, method)

        elif method in ("$", "$$") and obj:
            return self.convert_hashtable(n,i,c,obj,var)

        else:
            return self.convert_function_call(n,i,c)

    #def convert_block_context(self, n, i, c):
        #return NodeToJS.convert_block(self,n,i,c)

    def convert_context_block(self, n, i, c, ctx):
        skip_meta = False
        if len(n) > 0 and n[0].type == "SEMICOLON":
            meta = getattr(n[0].expression, "value", "")
            print "!", meta
            if meta[:3] == "@@@" and meta[-3:] == "@@@":
                skip_meta = True
                block_meta = ctx.parse_metadata(meta)
                for var, meta in block_meta["vars"].iteritems():
                    ctx.scope[var] = meta
                print block_meta["type"]

        k = 0
        o = []
        for x in n:
            if skip_meta and k == 0: 
                k += 1
                continue
            o.append(ctx.o(x,i+self.tab,c))
            k += 1

        return "{%s%s%s}" % (len(o) and ("\n"+i) or "", 
                (";\n"+i).join(o), len(o) and ("\n"+i[:-2]) or "")

    def convert_block(self, n, i, c):
        self.check(n,subnodes=len(n),attrs=["varDecls"])
        ctx = JSNodeToJoo(self)
        return self.convert_context_block(n,i,c,ctx)
        
    def parse_metadata(self, s):
        metadata = re.match(r'^@@@(.*[^@@@])@@@$', s)
        if not metadata: raise node2js.OtherError, "metadata error"
        metadata = metadata.group(1).split(";")
        block_meta = {"vars":{}}
        var_def = "(%s):?(%s)?#?(%s)?" % (varName_, varName_, varName_)
        var_def_regex = re.compile(r'(%s)' % var_def)
        let_def_regex = re.compile(r'let (%s)' % var_def)
        var_def_m = var_def_regex.match(metadata[0])
        for_let_block = re.match(r'for let (%s)' % var_def, metadata[0])
        for_each_let_block = re.match(r'for each let (%s,%s)' % (var_def, var_def), metadata[0])
        for_each_block = re.match(r'for each (%s,%s)' % (var_def, var_def), metadata[0])
        with_list = re.match(r'with_list (%s)' % varName_, metadata[0])
        with_block = re.match(r'with_block (%s)' % varName_, metadata[0])
        if for_let_block: 
            #print 1, metadata[0], for_let_block.groups()
            block_meta["type"] = "for let"
            i = for_let_block.groups()[1:]
            block_meta["iterator"] = {
                "var" : i[1],
                "alias" : i[0],
                "type" : i[2]
            }
            block_meta["vars"][i[1]] = block_meta["iterator"]
        elif for_each_let_block: 
            #print 2, metadata[0], for_each_let_block.groups()
            block_meta["type"] = "for each let"
            i = for_each_let_block.groups()[1:4]
            block_meta["iterator"] = {
                "var" : i[1],
                "alias" : i[0],
                "type" : i[2]
            }
            block_meta["vars"][i[1]] = block_meta["iterator"]
            o = for_each_let_block.groups()[4:]
            block_meta["object"] = {
                "var" : o[1],
                "alias" : o[0],
                "type" : o[2]
            }
            block_meta["vars"][o[1]] = block_meta["object"]
        elif for_each_block: 
            #print 3, metadata[0], for_each_block.groups()
            block_meta["type"] = "for each"
            i = for_each_block.groups()[1:4]
            block_meta["iterator"] = {
                "var" : i[1],
                "alias" : i[0],
                "type" : i[2]
            }
            block_meta["vars"][i[1]] = block_meta["iterator"]
            o = for_each_block.groups()[4:]
            block_meta["object"] = {
                "var" : o[1],
                "alias" : o[0],
                "type" : o[2]
            }
            block_meta["vars"][o[1]] = block_meta["object"]
        elif with_list: 
            #print 4, metadata[0], with_list.groups()
            block_meta["type"] = "with list"
            block_meta["var"] = with_list.groups()[0]
        elif with_block: 
            #print 5, metadata[0], with_block.groups()
            block_meta["type"] = "with block_meta"
            block_meta["var"] = with_block.groups()[0]
        elif var_def_m:
            #print 6, metadata[0], var_def_m.groups()
            block_meta["type"] = None
        elif let_def_m:
            #print 7, metadata[0], let_def_m.groups()
            block_meta["type"] = "let"
        else:
            raise node2js.OtherError, "metadata error"
            
        i = 0
        for x in metadata:
            if i == 0 and block_meta["type"] not in ("var", None):
                i += 1
                continue
            l = x.split(",")
            var = let_def_regex.match(l[0]) or var_def_regex.match(l[0])
            if var:
                var_ = var.groups()[1:]
                block_meta["vars"][var_[0]] = {
                    "alias" : var_[1],
                    "type" : var_[2]
                }
                for var_def_ in l[1:]:
                    var_ = var_def_regex.match(var_def_)
                    if not var_: raise node2js.OtherError, "metadata error"
                    var_ = var_.groups()[1:]
                    block_meta["vars"][var_[0]] = {
                        "alias" : var_[1],
                        "type" : var_[2]
                    }
            else:
                raise node2js.OtherError, "metadata error"
            i += 1

        return block_meta

    def convert_function_call(self, n, i, c):
        return NodeToJS.convert_call(self,n,i,c)

    # convert loops into range iterator
    def convert_for(self, n, i, c):
        self.check(n,attrs=["body","setup","condition","update","isLoop"])
        if n.setup.type == "ASSIGN" and n.condition.type == "GT" and \
                n.update.type == "INCREMENT":
            iterator_var = getattr(self.context_var(n.setup[0]), "value", None)
            condition_var = getattr(self.context_var(n.condition[0]), "value", None)
            update_var = getattr(self.context_var(n.update[0]), "value", None)
            if iterator_var and iterator_var == condition_var and \
                    iterator_var == update_var:

                return "for %s in range(%s) %s" % (iterator_var, self.o(n.condition[1], i, c), 
                            self.o(n.body, i, c))

        elif n.setup.type == "VAR" and len(n.setup) == 2 and \
                n.condition.type == "GT" and n.condition[0].type == "IDENTIFIER" and \
                n.condition[1].type == "IDENTIFIER" and n.update.type == "INCREMENT":
            iterator_var = n.setup[0].name
            condition_var = n.setup[1].name
            if iterator_var and iterator_var == n.condition[0].value and \
                    condition_var == n.condition[1].value:

                return "for %s in range(%s, %s) %s" % (iterator_var, 
                        self.o(n.setup[0].initializer, i, c), 
                        self.o(n.setup[1].initializer, i, c), 
                        self.o(n.body, i, c))

        elif n.setup.type == "ASSIGN" and n.condition.type == "GT" and \
                    n.update.type == "ASSIGN" and n.update.assignOp == 24:
            iterator_var = getattr(self.context_var(n.setup[0]), "value", None)
            condition_var =  getattr(self.context_var(n.condition[0]), "value", None)
            update_var = getattr(self.context_var(n.update[0]), "value", None)
            if iterator_var and \
                        condition_var == condition_var and \
                        update_var == iterator_var:

                return "for %s in range(%s, %s, %s) %s" % (iterator_var, 
                        self.o(n.setup[1], i, c), 
                        self.o(n.condition[1], i, c), 
                        self.o(n.update[1], i, c), 
                        self.o(n.body, i, c))

        return NodeToJS.convert_for(self,n,i,c)

    def context_var(self, n):
        if n.type in ("DOT", "INDEX"):
            for obj in ["group_obj", "unit_obj"]:
                if getattr(self, obj) and \
                        n[0].value in (getattr(self, obj), globals()["canonical_"+obj]) and \
                        n[1].value in self.scope and \
                        varNameRegex.match(n[1].value):

                    return n[1]

        elif n.type == "IDENTIFIER" and n.value in global_scope:
            return n

        return None

    def convert_dot(self, n, i, c):
        if not self.stack[-2]["node"] in ("DOT", "INDEX"):
            obj = n[0].value
            if self.unit_obj and obj == self.unit_obj and \
                    obj != canonical_unit_obj:
                n[0].name = n[0].value = canonical_unit_obj
            if self.root_obj and obj == self.root_obj and \
                    obj != canonical_root_obj:
                n[0].name = n[0].value = canonical_root_obj
            if self.glob_obj and obj == self.glob_obj and \
                    obj != canonical_glob_obj:
                n[0].name = n[0].value = canonical_glob_obj
            if self.group_obj and obj == self.group_obj and \
                    obj != canonical_group_obj:
                n[0].name = n[0].value = canonical_group_obj
            if self.class_obj and obj == self.class_obj and \
                    obj != canonical_class_obj:
                n[0].name = n[0].value = canonical_class_obj

        var = self.context_var(n)
        if var: 
            self.check(n,subnodes=2)
            return self.convert_context_var(var.value)

        if n.type == "DOT":
            return NodeToJS.convert_dot(self,n,i,c)
        elif n.type == "INDEX":
            return NodeToJS.convert_index(self,n,i,c)

    convert_index = convert_dot

    def convert_context_var(self, var_name, assign=False):
        if var_name not in self.scope: 
            return var_name
        var = self.scope[var_name]
        alias = var["alias"] or var_name
        varType = var["type"] 
        prfx = ""
        #if varType == "Dictionary":
            #prfx = "@"
            #varType = ""
        if assign:
            return "%s%s%s" % (prfx, alias, varType and (" "+varType))
        else:
            return "%s%s" % (prfx, alias)

    def convert_assign(self, n, i, c):
        var = self.context_var(n[0])
        if var: 
            var_name = var.value
            if self.is_role_node(n[1]):
                self.check(n, attrs=["assignOp"], subnodes=2)
                spec = len(n[1][1]) and n[1][1][0] or None
                #self.scope[role_name] = {}
                ctx = RoleContext(self, spec, var_name)
                return ctx.convert_role(i, c)

            elif var_name in self.scope:
                self.check(n, attrs=["assignOp"], subnodes=2)
                return "%s=%s" % (self.convert_context_var(var_name, True), 
                        self.o(n[1],i,c))

        return NodeToJS.convert_assign(self,n,i,c)

    def convert_new(self, n, i, c):
        self.check(n,subnodes=1)
        return "%s.%s()" % (self.o(n[0],i,c), n.value)

    def convert_new_with_args(self, n, i, c):
        self.check(n,subnodes=2)
        return "%s.%s(%s)" % (self.o(n[0],i,c), n.value, self.o(n[1],i,c))

    def convert_null(self, n, i, c):
        return "none"

    def convert_identifier(self, n, i, c):
        self.check(n, optattrs=["name"])
        if n.value == "undefined": return "none"
        if hasattr(n, "name"):
            var = self.scope.get(n.name)
            if var:
                alias = var["alias"] or n.name
                #varType = var["type"]
                #if varType and varType == "Dictionary": alias = "@"+alias
                n.name = n.value = alias
        return NodeToJS.convert_identifier(self, n, i, c)

    def convert_string(self, n, i, c):
        self.check(n,attrs=["quotes"])
        if n.value == "use strict": return "use strict"
        return NodeToJS.convert_string(self, n, i, c)


# Unit context
class UnitContext(JSNodeToJoo): 
    def __init__(self, parent, path_, file_, \
                    unit_obj, root_obj, glob_obj, body):
        JSNodeToJoo.__init__(self, parent)
        self.path = path_
        self.file_ = file_
        self.unit_obj = unit_obj
        self.root_obj = root_obj
        self.glob_obj = glob_obj
        self.body = body
        # FIXME context
        self.scope.update(unit_scope)

    def convert_unit(self, i, c):
        return self.o(self.body, i, c)

    def convert_script(self, n, i, c):
        self.check(n,attrs=["funDecls","varDecls","expDecls","modDecls",
                    "impDecls","loadDeps","hasEmptyReturn","hasReturnWithValue",
                    "isGenerator","isRootNode"], 
                    subnodes=len(n))
        return self.convert_context_block(n,i,c,self)
        #return JSNodeToJoo.convert_script(self, n, i, c)

    def convert_assign(self, n, i, c):
        var = self.context_var(n[0])
        if var:
            var_name = var.value
            #self.scope[var_name] = {}
            if self.is_function_node(n[1]):
                n[1].name = var_name
                self.check(n,attrs=["assignOp"],subnodes=2)
                return self.o(n[1], i, c)

            else:
                return JSNodeToJoo.convert_assign(self,n,i,c)
            
        return JSNodeToJoo.convert_assign(self,n,i,c)

    def context_var(self, n):
        if n.type in ("DOT", "INDEX"):
            if (n[0].type == "THIS" or n[0].value in (self.unit_obj, canonical_unit_obj)) and \
                    varNameRegex.match(n[1].value):
                return n[1]

        return JSNodeToJoo.context_var(self, n)

# Role context
class RoleContext(JSNodeToJoo):
    def __init__(self, parent, spec=None, roleName=""):
        JSNodeToJoo.__init__(self, parent)
        self.parent = parent
        self.spec = spec
        self.roleName = roleName
        self.role = {}
        self.unit_obj = parent.unit_obj

        if not spec or not len(spec): return 

        for prop in spec:
            assert prop.type == "PROPERTY_INIT", prop.type + " not implemented"
            name = prop[0].value
            fields = prop[1]
            assert fields.type in ("OBJECT_INIT", "DOT", "NULL", "NUMBER", "IDENTIFIER"), \
                    fields.type + " not implemented"
            self.role[name] = {"namespace":{}}
            if fields.type == "OBJECT_INIT":
                for field in fields:
                    field_name = field[0].value
                    field_value = field[1]
                    if field_name == "type":
                        self.role[name]["type"] = self.node_to_type(field_value)

                    elif field_name == "argumentType":
                        assert field_value.type == "ARRAY_INIT", "unexpected " + field_value.type
                        argumentType = []
                        for x in field_value:
                            argumentType.append(self.node_to_type(x))
                        self.role[name]["argumentType"] = argumentType

                    elif field_name == "constructor":
                        assert field_value.type in ("TRUE", "FALSE"), "unexpected " + field_value.type
                        self.role[name]["constructor"] = field_value.type == "TRUE"

                    elif field_name == "defaultArguments":
                        assert field_value.type == "ARRAY_INIT", "unexpected " + field_value.type
                        defaultArguments = []
                        for x in field_value:
                            defaultArguments.append(self.o(x))
                        self.role[name]["defaultArguments"] = defaultArguments

                    elif field_name == "returnType":
                        self.role[name]["returnType"] = self.node_to_type(field_value)

                    elif field_name == "restArgument":
                        assert field_value.type in ("TRUE", "FALSE"), "unexpected " + field_value.type
                        self.role[name]["restArgument"] = field_value.type == "TRUE"

                    elif field_value.type in ("TRUE", "FALSE"):
                        self.role[name]["namespace"][field_name] = field_value.type == "TRUE"

                    else:
                        raise node2js.OtherError, "unknown role field " + field_name

            else:
                self.role[name]["type"] = self.node_to_type(fields)

    def convert_role(self, i, c):
        #self.check(self.role, attrs=["destructurings"], subnodes=1)
        s = []
        for name, spec in self.role.iteritems():
            ns = []
            for field, flag in spec["namespace"].iteritems():
                if flag == True:
                    ns.append(field)
            type_ = ""
            if spec["type"]:
                type_ = type_to_class(spec["type"])
            if spec.get("constructor"):
                ns.append("constructor") 
            s.append((" ".join(ns) + " " + name + " " + type_).strip())

        return "role %s {%s}" % (self.roleName, 
                len(s) and "\n"+i+(",\n"+i).join(s)+"\n"+i[:-2] or "")

    def node_to_type(self, n):
        if n.type == "DOT":
            type_obj = self.parent.context_var(n[0])
            if type_obj and type_obj.value == "Type":
                assert n[1].type == "IDENTIFIER", "unexpected " + n[1].type
                return type_[n[1].value]

        elif n.type == "IDENTIFIER":
            return n.value

        elif n.type == "NULL":
            return None

        elif n.type == "NUMBER":
            return int(n.value)

        raise AssertionError, "type error"

    __contains__ = lambda self, k: k in self.role.keys()
    __getitem__ = lambda self, k: self.role.get(k)

# Package context
class PackageContext(JSNodeToJoo):
    def __init__(self, parent, args):
        JSNodeToJoo.__init__(self, parent)
        self.virtualNamespaces = jooparser.PackageContext.virtualNamespaces

        fun = None
        exports = None
        if len(args) == 1:
            assert args[0].type in ("STRING", "FUNCTION"), "unexpected " + args[1].type

        elif len(args) == 2:
            assert args[0].type in ("STRING", "FUNCTION"), "unexpected " + args[0].type
            assert args[1].type in ("FUNCTION", "OBJECT_INIT"), "unexpected " + args[1].type

        elif len(args) == 3:
            assert args[0].type == "STRING", "unexpected " + args[0].type
            assert args[1].type == "FUNCTION", "unexpected " + args[1].type
            assert args[2].type == "OBJECT_INIT", "unexpected " + args[2].type

        self.group = None
        self.exports = None
        self.pack = None

        if len(args) >= 1:
            if args[0].type == "STRING":
                self.group = args[0].value
            elif args[0].type == "FUNCTION":
                fun = args[0]

        if len(args) >= 2:
            if args[1].type == "FUNCTION":
                fun = args[1]
            elif args[1].type == "OBJECT_INIT":
                exports = args[1]

        if len(args) == 3: exports = args[2]
            
        if fun: 
            if len(fun.params)>0:
                self.group_obj = fun.params[0]
                """
                if group_obj != joo_node2js.JooNodeToJS.group_obj:
                    defs.err("WARNING: group object name is differ from " + 
                                    joo_node2js.JooNodeToJS.group_obj)
                """
            self.pack = fun.body

        if exports: 
            self.exports = RoleContext(parent, exports)

    def convert_package(self, i, c):
        return "package %s%s" % (self.group and self.group + " " or "", 
                self.pack and self.o(self.pack, i, c) or "{}")

    def convert_script(self, n, i, c):
        return JSNodeToJoo.convert_script(self, n, i, c)

    def convert_assign(self, n, i, c):
        var = self.context_var(n[0])
        if var and var.value in self.exports:
            var_name = var.value
            self.check(n,attrs=["assignOp"],subnodes=2)
            # fixme save context
            #self.scope[var_name] = {}
            if self.is_function_node(n[1]):
                n[1].name = var_name
                initializer = self.o(n[1], i, c)

            elif self.is_class_node(n[1]):
                ctx = ClassContext(self, n[1][1])
                if ctx.className == var_name:
                    initializer = ctx.convert_class(i, c)

            else:
                initializer = var_name + "=" + self.o(n[1], i, c)

            # export 
            return "public %s" % initializer
                
        return JSNodeToJoo.convert_assign(self,n,i,c)

    def context_var(self, n):
        if n.type in ("DOT", "INDEX"):
            if n[0].value in ("this", self.group_obj, canonical_group_obj) and \
                    n[1].value in self.exports and \
                    varNameRegex.match(n[1].value):
                return n[1]

        return JSNodeToJoo.context_var(self, n)

# Class context
class ClassContext(JSNodeToJoo):
    def __init__(self, parent, args):
        JSNodeToJoo.__init__(self, parent)
        self.virtualNamespaces = jooparser.ClassContext.virtualNamespaces
        self.ns = []
        self.className = ""
        self.role = {}
        self.ancestors = []
        self.classBody = None

        # class name
        if len(args) >= 1:
            assert args[0].type in ("STRING", "NULL"), "class name: expected string or null"
            if args[0].type == "STRING":
                self.className = args[0].value

        # ancestors
        if len(args) >= 2:
            assert args[1].type in ("ARRAY_INIT", "NULL"), "class ancestors: expected array or null"
            if args[1].type == "ARRAY_INIT" and len(args[1]) > 0:
                self.ancestors = args[1]

        self.class_obj = None

        # class body
        if len(args) >= 3:
            assert args[2].type in ("FUNCTION", "NULL"), "class body: expected function or null"
            if args[2].type == 'FUNCTION':
                if len(args[2].params) > 0:
                    self.class_obj = args[2].params[0]
                    """
                    if self.class_obj != joo_node2js.JooNodeToJS.class_obj:
                        defs.err("WARNING: class object name is differ from " + 
                                        joo_node2js.JooNodeToJS.class_obj)
                    """
                self.classBody = args[2].body

        # role
        if len(args) == 4:
            # TODO check that role is object, in any other case required
            # that node will be evaluated, but it is not implemented at this moment
            spec = args[3]
            #assert spec.type == "OBJECT_INIT", \
            #    "class role: " + spec.type + " not implemented"

            if spec.type == "OBJECT_INIT":
                self.role = RoleContext(self, spec)
            else:
                self.role = self.o(spec)


    def convert_class(self, i, c):
        className = self.className
        ancestors = len(self.ancestors) and \
                        ("(%s)" % ", ".join([ self.o(x,i,c) for x in self.ancestors ])) or ""
        classBody = self.classBody and self.o(self.classBody,i,c) or "{}"
        as_role = ""
        if type(self.role) == str:
            as_role = " as " + self.role
        ns = ""
        return "%sclass %s%s%s%s" % (ns, className, ancestors, classBody, as_role)

    def convert_script(self, n, i, c):
        return JSNodeToJoo.convert_script(self, n, i, c)

    def convert_assign(self, n, i, c):
        var = len(n) and self.context_var(n[0])
        if var:
            var_name = var.value
            self.check(n,attrs=["assignOp"],subnodes=2)
            initializer = n[1]
            spec = self.role[var_name]
            if spec: ns = " ".join([field for field, flag in spec["namespace"].iteritems() if flag == True ])
            else: ns = ""
            if ns: ns += " "
            # FIXME move out all renderings inside FunctionContext
            if spec and spec["type"] & type_["FUNCTION"]:
                assert initializer.type == "FUNCTION", "unexpected " + initializer.type
                initializer.name = var_name
                assert len(initializer.params) > 0, "params has to be at least one argument"
                if spec.get("restArgument"):
                    initializer.restArgument = True

                argType = spec.get("argumentType")
                if argType:
                    for k in range(1, len(argType)+1):
                        n = k-1
                        if not argType[n]: continue
                        if type(argType[n]) is int:
                            type__ = type_to_class(argType[n])
                        else:
                            type__ = argType[n]

                        initializer.params[k] = "%s %s" % (initializer.params[k], type__)

                if spec.get("returnType") != None:
                    initializer.returnType = spec.get("returnType")

                if spec.get("constructor"):
                    initializer.constructor = True

                defArgs = spec.get("defaultArguments")
                if defArgs:
                    params = initializer.params
                    for k in range(len(defArgs), 0, -1):
                        params[-k] += "="+defArgs[-k]
                
                return ns + self.o(initializer, i, c)

            else:
                type__ = ""
                if spec and "type" in spec:
                    # maybe 0 which is none type
                    x = spec.get("type")
                    if x: type__ = " " + type_to_class(x)
                return ns + var_name + type__ + "=" + self.o(initializer, i, c)

        return JSNodeToJoo.convert_assign(self, n, i, c)

    def convert_function(self, n, i, c):
        #self.check(n,subnodes=2)
        return JSNodeToJoo.convert_function(self, n, i, c)

    def context_var(self, n):
        if n.type in ("DOT", "INDEX"):
            if n[0].type == "THIS" and n[1].value in self.role and \
                    varNameRegex.match(n[1].value):
                return n[1]

        return JSNodeToJoo.context_var(self, n)

# Function context 
class FunctionContext(JSNodeToJoo):
    def __init__(self, parent, fun):
        JSNodeToJoo.__init__(self, parent)
        self.fun = fun
        # check for single expressions
        if len(fun.body) == 1 and fun.body[0].type == "RETURN":
            self.singleExpression = True
            self.body = fun.body[0].value
        else:
            self.singleExpression = False
            self.body = fun.body
        self.params = []
        self.params.extend(fun.params)
        if getattr(fun, "restArgument", False):
            self.params[-1] = "*"+self.params[-1]
        self.name = getattr(fun, "name", "")
        self.functionForm = fun.functionForm
        self.returnType = getattr(fun, "returnType", None)
        self.constructor = getattr(fun, "constructor", False)

    def convert_function(self, i, c):
        self.check(self.fun, attrs=["functionForm","params","body"], 
                              optattrs=["name", "returnType", "restArgument", "constructor"])
        if not self.singleExpression:
            assignName = ""
            funName = self.name
        else:
            assignName = self.name
            funName = ""

        if self.returnType:
            funName = funName + " " + type_to_class(self.returnType)

        if assignName: assignName += "="

        function = self.constructor and "constructor" or "function"

        print self.body
        if self.functionForm == 0:
            return "%s%s %s(%s) %s" % (assignName, function, funName,
                    ", ".join(par for par in self.params),
                    self.o(self.body,i,c))
        else:
            return "%s%s %s(%s) %s" % (assignName, function, funName, 
                    ",".join(self.params), 
                    self.o(self.body,i,c))

    #def convert_identifier(self, n, i, c):
        #return JSNodeToJoo.convert_identifier(self, n, i, c)

    #def convert_script(self, n, i, c):
        #return JSNodeToJoo.convert_script(self, n, i, c)

    def context_var(self, n):
        if n.type in ("DOT", "INDEX"):
            if n[0].type == "THIS" and \
                    varNameRegex.match(n[1].value):
                return n[1]

        return JSNodeToJoo.context_var(self, n)

    def convert_identifier(self, n, i, c):
        #if n.value in self.varSubs:
            #n.value = n.name = self.varSubs[n.value]
        return JSNodeToJoo.convert_identifier(self,n,i,c)

def convert(parsetree, include_props=False):
    ctx = JSNodeToJoo(None)
    return ctx.convert(parsetree, "", {"include_props": include_props}) + "\n"
